package me.zhengjie.gas.proto;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import me.zhengjie.gas.proto.recv.*;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
 * 基础通讯包
 */
public class TankerComm {

    // 数据包头
    private byte HEAD = (byte)0xFA;

    // 枪号
    protected byte NO;

    // 命令字
    protected byte Cmd;

    // 数据内容
    protected byte[] DATA;

    protected int CRC;

    protected static int crc(byte[] buffer, int len) {
        int crc = 0;
        for (int i=0; i< len; i++) {
            byte a = buffer[i];
            byte b = (byte) (crc & 0xFF);
            b = (byte) (a ^ b);
            crc = ((crc & 0xFF00) | (0xFF & b));

            for (int c=0; c<8; c++) {
                b = (byte) (crc & 0xFF);
                crc = (crc >> 1);
                if ( (b & 0x01) != 0 ) {
                    crc = ((crc ^ 0xA001) & 0xFFFF);
                }
            }

        }

        //CRC的结果可能会出现FA，这个FA是我通讯的命令头，所以通讯时候，把CRC的结果高位字节和低位字节都合7F与一下，
        //这样防止通讯时候出现CRC的结果这两个 字节出现FA
        //if ((crc & 0xFF) == 0xFA || ((crc & 0xFF00) >> 8) == 0xFA) {
        crc = crc & 0x7F7F;
        //}
        return crc;
    }

    public void test() {
        byte[] buf = new byte[] {(byte) 0xFA, 0x01, 0x0D, (byte) 0xB0, 0x23, 0x07, 0x01, 0x10, 0x10, 0x40, 0x03, 0x04};
        int crc = crc(buf, buf.length);
        byte[] data = new byte[2];
        data[0] = (byte) (crc & 0xFF);
        data[1] = (byte) ((crc & 0xFF00) >> 8);
    }

    // 组包
    protected byte[] encode() {
        int len = 5 + DATA.length;
        byte[] data = new byte[len];
        data[0] = HEAD;
        data[1] = NO;
        data[2] = (byte)((DATA.length + 2) & 0xFF);
        System.arraycopy(DATA, 0, data, 3, DATA.length);
        int crc = crc(data, len-2);

        // 填写CRC校验位
        data[len - 1] = (byte)(crc & 0x7F);
        data[len - 2] = (byte)((crc & 0x7F00) >> 8);

        return data;
    }

    // 解包
    protected boolean decode() {
        return true;
    }

    // 数据解包
    public static TankerComm parse(byte[] data) throws RuntimeException {
        // 验证包头
        if (data[0] != (byte)0xFA)
            throw new RuntimeException("包头校验不通过!");

        int len = data.length;

        // CRC校验检查
        int crc = (data[len - 2] << 8) | (data[len - 1]) & 0xFFFF;
        int ck = crc(data, len - 2);
        if (crc != ck)
            throw new RuntimeException("CRC校验不通过!");

        byte no = data[1];
        byte[] buf = new byte[data[2]];
        System.arraycopy(data, 3, buf, 0, (byte)data[2]);
        byte cmd = buf[0];

        TankerComm comm = null;
        switch (cmd) {
            case (byte) 0xA1: {
                comm = new A1Comm();
                break;
            }
            case (byte) 0xA3: {
                comm = new A3Comm();
                break;
            }
            case (byte) 0xB3: {
                comm = new B3Comm();
                break;
            }
            case (byte) 0xBA: {
                comm = new BAComm();
                break;
            }
            case (byte) 0xE3: {
                comm = new E3Comm();
                break;
            }
            case (byte) 0xE4: {
                comm = new E4Comm();
                break;
            }
            case (byte) 0xE5: {
                comm = new E5Comm();
                break;
            }
            case (byte) 0xE6: {
                comm = new E6Comm();
                break;
            }
            case (byte) 0xE7: {
                comm = new E7Comm();
                break;
            }
            case (byte) 0xE8: {
                comm = new E8Comm();
                break;
            }
            default: {
                throw new RuntimeException("无效的命令字!");
            }
        }

        comm.CRC = crc;

        // 设置枪号
        comm.setNO((byte) no);

        // 解析数据
        comm.setDATA(buf);
        if (!comm.decode())
            throw new RuntimeException("包数据解析失败");

        return comm;
    }

    /**
     * 设置枪号
     * @param no
     */
    public void setNO(byte no) {
        this.NO = no;
    }

    public byte getNO() {
        return NO;
    }

    public void setDATA(byte[] data) {
        DATA = new byte[data.length];
        System.arraycopy(data, 0, DATA, 0, data.length);
    }

    public byte[] getDATA() {
        return DATA;
    }

    public byte[] stringToBcdByteArray(String str, int len) {
        String bcdString = String.format("%0" + len*2+ "d", Long.parseLong(str));
        int length = (bcdString.length() / 2);
        byte[] result = new byte[len];

        for (int i = 0; i < len; i++) {
            if (i<length) {
                byte upper = (byte) Character.digit(bcdString.charAt(i*2), 16);
                byte lower = (byte) Character.digit(bcdString.charAt(i*2 + 1), 16);
                result[i] = (byte) ((upper << 4) | lower);
            } else {
                result[i] = 0;
            }
        }

        return result;
    }

    public static long byteToBcd(byte[] data, int pos, int len) {
        long num = 0;
        for (int i = 0; i<len; i++) {
            byte b = data[pos + i];
            int power = (len - i - 1) * 2;

            long u = (long) ((byte)((b & 0xF0) >> 4) * Math.pow(10, power + 1));
            long l = (long) (+ (byte)(b & 0x0F) * Math.pow(10, power));
            num += u + l;
        }

        return num;
    }

    public static String byteToBcdString(byte[] data, int pos, int len) {
        long value = byteToBcd(data, pos, len);
        return String.format("%d", value);
    }

    public static Calendar byteToDateTime(byte[] data, int pos, int len) throws ParseException {
        long value = byteToBcd(data, pos, len);

        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        Date date = sdf.parse(String.valueOf(value));
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);

        return calendar;
    }

    public static long byteToHex(byte[] data, int pos, int len) {
        long num = 0;
        for (int i=0; i<len; i++) {
            byte b = data[pos + i];
            num = (num << 8) | (b & 0xFF);
        }

        return num;
    }

    public static String byteToHexString(byte[] data, int pos, int len) {
        String str = "";
        for (int i=0; i<len; i++) {
            byte b = data[pos + i];
            str += String.format("%02X ", b);
        }

        return str.trim();
    }

    public String toString() {
        StringBuffer buffer = new StringBuffer();
        buffer.append(String.format("包头: 0x%02X\n", HEAD));
        buffer.append(String.format("枪号: 0x%02X\n", NO));
        buffer.append(String.format("命令: 0x%02X\n", Cmd));
        buffer.append(String.format("数据长度: 0x%d\n", DATA.length));
        buffer.append(String.format("校验码: 0x%02X%02X\n", ((CRC & 0xFF00) >> 8), CRC & 0xFF));


        return buffer.toString();
    }

    public String toJSON() {
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return objectMapper.writeValueAsString(TankerComm.this);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }
}
